<template>
  <div class="gantt-chart" ref="ganttChart">
    <div
        v-if="menuVisible"
        class="Z-Menu"
        @mousedown.prevent
        :style="{
        top: menuY+'px',
        left: menuX+ 'px',
      }"
    >
      <ul>
        <li v-show="taskOperation" @click="moveCurrentBlock">选中</li>
        <li v-show="taskOperation" @click="switchBlock">交换</li>
        <li v-show="rowOperation" @click="pasteBlock">粘贴</li>
      </ul>
    </div>
    <div
        class="gantt-container"
        :style="{
        height: `100%`,
        width: `100%`
      }"
    >
      <div v-show="!hideHeader" class="gantt-header" :style="{ width: `100%` }">
        <div
            class="gantt-header-title"
            :style="{
            'line-height': titleHeight + 'px',
            height: titleHeight + 'px',
            width: titleWidth + 'px'
          }"
        >
          <div class="date-control">
            <!--            <span class="el-icon-caret-left btn-date-ctrl" @click="scrollPreDay"></span>-->
            <el-icon class="btn-date-ctrl" @click="ScrollBackQuicklyDay">
              <PlayBackIcon/>
            </el-icon>

            <el-icon class="btn-date-ctrl" @click="scrollPreDay">
              <CaretBackOutlineIcon/>
            </el-icon>

            <span class="current-date">{{ currentDay?.format('MM-DD') }}</span>
            <!--            <span class="el-icon-caret-right btn-date-ctrl" @click="scrollNextDay"></span>-->
            <el-icon class="btn-date-ctrl" @click="scrollNextDay">
              <CaretForwardIcon/>
            </el-icon>
            <el-icon class="btn-date-ctrl" @click="ScrollorwardFastDay">
              <PlayForwardIcon/>
            </el-icon>
          </div>
        </div>
        <div class="gantt-header-timeline">
          <div
              ref="headerTimeline"
              class="gantt-header-timeline-container"
              :style="{ width: totalWidth + 'px' }"
          >
            <timeline
                :start="start"
                :end="end"
                :cellWidth="cellWidth"
                :titleHeight="titleHeight"
                :scale="scale"
                :startTimeOfRenderArea="dayjs(startTimeOfRenderArea)"
                :endTimeOfRenderArea="dayjs(endTimeOfRenderArea)"
                :getPositionOffset="getPositionOffset"
                :currentTime="currentDay"
            >
              <template v-slot="{ day, getTimeScales }">
                <slot name="timeline" :day="day" :getTimeScales="getTimeScales">
                </slot>
              </template>
            </timeline>
          </div>
        </div>
      </div>

      <div
          class="gantt-body"
          ref="gBoddy"
          :style="{ height: `calc(100% - ${actualHeaderHeight}px)` }"
      >
        <div class="gantt-table">
          <div
              ref="marklineArea"
              :style="{ marginLeft: titleWidth + 'px' }"
              class="gantt-markline-area"
          >
            <CurrentTime
                v-if="showCurrentTime"
                :getPositionOffset="getPositionOffset"
            />
            <mark-line
                v-for="(timeConfig, index) in timeLines"
                :key="index"
                :timeConfig="timeConfig"
                :getPositionOffset="getPositionOffset"
            >
              <template v-slot="{ timeConfig, getPosition }">
                <slot
                    name="markLine"
                    :timeConfig="timeConfig"
                    :getPosition="getPosition"
                ></slot>
              </template>
            </mark-line>
          </div>
          <div
              class="gantt-leftbar-container"
              :style="{
              width: titleWidth + 'px'
            }"
          >
            <div class="left-scroll-wrapper" ref="leftBarWrapper">
              <LeftBar
                  v-for="(blockGroup, index) in datas"
                  :key="index"
                  :datas="blockGroup.children || []"
                  :groupType="blockGroup.groupType || {}"
                  :group-index="index"
                  :is-open="blockGroup.isOpen"
                  :dataKey="dataKey"
                  :scrollTop="scrollTop"
                  :unVisibleHeight="unVisibleHeight"
                  :totalHeight="totalHeight"
                  :heightOfBlocksWrapper="heightOfBlocksWrapper"
                  :cellHeight="cellHeight"
                  :preload="preload"
                  :isShowStatistics="isShowStatistics"
              >
                <template v-slot="{ rowData }">
                  <MenuItem :rowData="rowData"></MenuItem>
                </template>
              </LeftBar>
            </div>
          </div>
          <div ref="blocksWrapper" class="gantt-blocks-wrapper">
            <div class="scroller"
                 :style="{ width: totalWidth <= (blocksWrapperWidth - titleWidth) ? (blocksWrapperWidth - titleWidth) + 'px' : totalWidth + 'px' }">
              <BlockGroup
                  v-for="(blockGroup, index) in datas"
                  :key="index"
                  :datas="blockGroup.children || []"
                  :group-index="index"
                  :is-open="blockGroup.isOpen"
                  :scrollTop="scrollTop"
                  :scrollLeft="scrollLeft"
                  :unVisibleHeight="unVisibleHeight"
                  :totalHeight="totalHeight"
                  :heightOfBlocksWrapper="heightOfBlocksWrapper"
                  :cellWidth="cellWidth"
                  :cellHeight="cellHeight"
                  :scale="scale"
                  :startTimeOfRenderArea="startTimeOfRenderArea"
                  :endTimeOfRenderArea="endTimeOfRenderArea"
                  :preload="preload"
                  :style="{ width: totalWidth + 'px' }"
              >
                <template v-slot:BlockRow="{ rowData, showList }">
                  <BlockRow
                      :cellHeight="cellHeight"
                      :key="rowData.id"
                      :rowData="rowData"
                      :showList="showList"
                      :isShowStatistics="isShowStatistics"
                      @dragover.native.prevent
                      @drop.native="dropToRow($event, rowData, index)"
                      @contextmenu.prevent="handleRightClickRow($event, rowData)"
                  >
                    <template v-slot:blockItem="{ blockData }">
                      <TaskItem
                          :getPositionOffset="getPositionOffset"
                          :getWidthAbout2Times="getWidthAbout2Times"
                          :currentTime="currentTime"
                          :cellHeight="cellHeight"
                          :key="blockData.id"
                          :blockData="blockData"
                          @dragover.native.prevent
                          @pointerdown.native.stop
                          @contextmenu.native.stop
                          @mousedown.native.left.stop="handleLeftClickBlock($event, rowData, blockData)"
                          @contextmenu.prevent="handleRightClickBlock($event, rowData, blockData)"
                      />
                    </template>
                  </BlockRow>
                </template>
              </BlockGroup>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import {ref, onMounted, onBeforeUnmount, watch, PropType, computed} from 'vue';
import dayjs from 'dayjs';
import IScroll from 'iscroll/build/iscroll-probe';
import {calcScalesAbout2Times, getBeginTimeOfTimeLine, isDayScale, scaleList} from '@/utils/timeLineUtils.ts';
import {throttle, noop, warn} from '@/utils/tools.ts';
import {getPositionOffset as _getPositionOffset, getWidthAbout2Times as _getWidthAbout2Times} from '@/utils/gtUtils';
import ResizeObserver from "resize-observer-polyfill";
import Timeline from './time-line/index.vue';
import CurrentTime from './mark-line/current-time.vue';
import LeftBar from './left-bar/index.vue';
import BlockGroup from './block-group/block-group.vue';
import BlockRow from './block-row/block-row.vue';
import MarkLine from './mark-line/index.vue';
import TaskItem from './demo/task-item.vue';
import MenuItem from './demo/menu-item.vue';
import {useGanttSettingStore} from '@/store/modules/ganttSettingStore/ganttSettingStore.ts';
import {icon} from '@/plugins/icon.ts'
import {ElMessage} from "element-plus";

const {CaretBackOutlineIcon, CaretForwardIcon, PlayBackIcon, PlayForwardIcon} = icon.ionicons5


// 定义 props
const props = defineProps({
  currentTime: {
    type: Object as PropType<dayjs.Dayjs>,
    default: () => dayjs()
  },
  startTime: {
    type: Object as PropType<dayjs.Dayjs>,
    default: () => dayjs(),
    validator: (date: dayjs.Dayjs | string | number | Date | undefined) => {
      const isValid = dayjs(date).isValid();
      if (!isValid) {
        console.warn(`非法的开始时间 ${date}`);
      }
      return isValid;
    }
  },
  endTime: {
    type: Object as PropType<dayjs.Dayjs>,
    default: () => dayjs(),
    validator: (date: dayjs.Dayjs | string | number | Date | undefined) => {
      const isValid = dayjs(date).isValid();
      if (!isValid) {
        console.warn(`非法的结束时间 ${date}`);
      }
      return isValid;
    }
  },
  enableGrab: {
    type: Boolean,
    default: true
  },
  cellWidth: {
    type: Number,
    default: 50
  },
  cellHeight: {
    type: Number,
    default: 50
  },
  titleHeight: {
    type: Number,
    default: 60
  },
  titleWidth: {
    type: Number,
    default: 250
  },
  scale: {
    type: Number,
    default: 60,
    validator: (value: number) => scaleList.includes(value) || isDayScale(value)
  },
  datas: {
    type: Array as PropType<any[]>, // 你可能需要更具体的类型定义
    default: () => []
  },
  dataKey: {
    type: String,
    default: undefined
  },
  itemKey: {
    type: String,
    default: undefined
  },
  showCurrentTime: {
    type: Boolean,
    default: false
  },
  timeLines: {
    type: Array as PropType<any[]>, // 你可能需要更具体的类型定义
    default: () => []
  },
  scrollToTime: {
    validator: (date: dayjs.Dayjs | string | number | Date | undefined) => dayjs(date).isValid()
  },
  hideHeader: {
    type: Boolean,
    default: false
  },
  timeRangeCorrection: {
    type: Boolean,
    default: true
  },
  preload: {
    type: Number,
    default: undefined // 或者你可以设置一个具体的默认值
  },
  isShowStatistics: {
    type: Boolean,
    default: () => {
      return false
    }
  }
});

const menuVisible = ref(false)
const menuY = ref(0)
const menuX = ref(0)
const ganttChart = ref()
const taskOperation = ref(false)
const rowOperation = ref(false)

const emit = defineEmits<{
  (event: 'scrollToPosition', position: { x: number, y: number }): void;
  (event: 'refresh'): void;
  (event: 'dragTask'): void; // 添加 dragTask 事件的定义
}>();

const ganttStore = useGanttSettingStore();

const selector = ref({
  gantt_leftbar: null,
  gantt_table: null,
  gantt_timeline: null,
  gantt_markArea: null,
});

const scrollTop = ref(0);
const scrollLeft = ref(0);
const heightOfBlocksWrapper = ref(0);
const widthOfBlocksWrapper = ref(0);
const currentDay = ref(dayjs());
let scroller: IScroll | null = null; // 定义 scroller 变量

const cellWidth = ref<number>(60); // 假设默认宽度为100
// const scale = ref<number>(60); // 假设默认比例为1

const blocksWrapper = ref<HTMLElement | null>(null);
const gBoddy = ref<HTMLElement | null>(null);
const blocksWrapperWidth = ref(0);
const leftBarWrapper = ref<HTMLElement | null>(null);
const headerTimeline = ref<HTMLElement | null>(null);
const marklineArea = ref<HTMLElement | null>(null);

const observeContainer = throttle((entries) => {
  entries.forEach((entry) => {
    const cr = entry.contentRect;
    heightOfBlocksWrapper.value = cr.height;
    widthOfBlocksWrapper.value = cr.width;
  });
});

const observer = new ResizeObserver(observeContainer);


// 这段有性能问题，多次计算重绘页面会感觉有些卡顿感
const start = computed(() => dayjs(props.startTime));

const end = computed(() => {
  const localStart = start.value;
  let localEnd = dayjs(props.endTime);
  const totalWidth = calcScalesAbout2Times(localStart, localEnd, props.scale) * cellWidth.value;
  if (props.timeRangeCorrection && (localStart.isAfter(localEnd) || totalWidth <= widthOfBlocksWrapper.value)) {
    localEnd = getBeginTimeOfTimeLine(localStart, props.scale).add((widthOfBlocksWrapper.value / cellWidth.value) * props.scale, 'minute');
  }
  return localEnd;
});


const totalWidth = computed(() => cellWidth.value * totalScales.value);

const totalScales = computed(() => calcScalesAbout2Times(start.value, end.value, props.scale));

const totalHeight = computed(() => {
  let height = 0;
  for (let i = 0; i < props.datas.length; i++) {
    let rowLength = props.datas[i].isOpen ? props.datas[i].children.length + 1 : 1;
    height += rowLength * props.cellHeight;
  }
  return height;
});

const beginTimeOfTimeLine = computed(() => getBeginTimeOfTimeLine(start.value, props.scale));

const beginTimeOfTimeLineToString = computed(() => beginTimeOfTimeLine.value.toString());

const unVisibleHeight = computed(() => window.innerHeight - heightOfBlocksWrapper.value);

const availableScrollLeft = computed(() => {
  const {value: totalWidthVal} = totalWidth;
  return totalWidthVal - widthOfBlocksWrapper.value - 1;
});

const availableScrollTop = computed(() => {
  const {value: totalHeightVal} = totalHeight;
  return totalHeightVal - heightOfBlocksWrapper.value - 1;
});

const actualHeaderHeight = computed(() => (props.hideHeader ? 0 : props.titleHeight));

const startTimeOfRenderArea = computed(() => {
  if (heightOfBlocksWrapper.value === 0) {
    return null;
  }
  return beginTimeOfTimeLine.value.toDate().getTime() + (scrollLeft.value / cellWidth.value) * props.scale * (60 * 1000);
});

const endTimeOfRenderArea = computed(() => {
  if (heightOfBlocksWrapper.value === 0) {
    return null;
  }
  const renderWidth = totalWidth.value < widthOfBlocksWrapper.value ? totalWidth.value : widthOfBlocksWrapper.value;
  return beginTimeOfTimeLine.value.toDate().getTime() + ((scrollLeft.value + renderWidth) / cellWidth.value) * props.scale * (60 * 1000);
});

onMounted(() => {
  if (gBoddy.value) {
    blocksWrapperWidth.value = gBoddy.value.offsetWidth
  }

  cacheSelector();
  observer.observe(blocksWrapper.value);

  scroller = new IScroll(blocksWrapper.value, {
    probeType: 3,
    click: true,
    scroll: true,
    scrollX: true,
    scrollY: true,
    freeScroll: false,
    mouseWheel: true,
    mousedown: true,
    scrollbars: true,
    interactiveScrollbars: true,
    fadeScrollbars: false,
    useTransition: true,
  });

  // setTimeout(() => {
  //   scroller.refresh();
  // }, 1000);
  scroller.on('scroll', scrollHandler);

  ganttStore.scrollToPosition((position) => {
    return position
  });

  ganttStore.refresh();

});

onBeforeUnmount(() => {
  observer.disconnect();
  releaseSelector();
  if (scroller) {
    scroller.destroy(); // 销毁 IScroll 实例
    scroller = null;
  }
});

const scrollHandler = () => {
  selector.value.gantt_timeline.style.transform = `translateX(${scroller.x}px)`;
  selector.value.gantt_leftbar.style.transform = `translateY(${scroller.y}px)`;
  selector.value.gantt_markArea.style.left = scroller.x + 'px';
  scrollLeft.value = -scroller.x;
  scrollTop.value = -scroller.y;

  const width = scroller.x;
  const mileSeconds = -(width / props.cellWidth) * props.scale * 60 * 1000;
  const scrollTime = new Date(props.startTime).getTime() + mileSeconds;
  currentDay.value = dayjs(scrollTime);
};

const scrollPreDay = () => {
  let tempDay = currentDay.value;
  let startTime: dayjs.Dayjs = dayjs(props.startTime);

  startTime = startTime.subtract(1, 'hour');
  tempDay = tempDay.subtract(1, 'day').set('hour', 0).set('minute', 0);

  if (tempDay.isBefore(startTime)) {
    //tempDay 时间是否早于startTime
    ElMessage.warning('不能查看开始时间之前')
    return false;
  }

  currentDay.value = tempDay;
  const width = getWidthAbout2Times(props.startTime, tempDay);
  scroller.scrollTo(-width, scroller.y, 400);
};

const scrollNextDay = () => {
  let tempDay = currentDay.value;
  let endTime = dayjs(props.endTime);
  endTime = endTime.subtract(1, 'hour');
  tempDay = tempDay.add(1, 'day').set('hour', 0).set('minute', 0);

  if (tempDay.isAfter(endTime)) {
    ElMessage.warning('不能查看结束时间之后')
    return false;
  }

  currentDay.value = tempDay;
  const width = getWidthAbout2Times(props.startTime, tempDay);
  scroller.scrollTo(-width, scroller.y, 400);
};

const ScrollBackQuicklyDay = () => {
  let tempDay = currentDay.value;
  let startTime: dayjs.Dayjs = dayjs(props.startTime);

  startTime = startTime.subtract(1, 'hour');
  tempDay = tempDay.subtract(3, 'day').set('hour', 0).set('minute', 0);

  if (tempDay.isBefore(startTime)) {
    ElMessage.warning('不能查看开始时间之前')
    return false;
  }

  currentDay.value = tempDay;
  const width = getWidthAbout2Times(props.startTime, tempDay);
  scroller.scrollTo(-width, scroller.y, 400);
};

const ScrollorwardFastDay = () => {
  let tempDay = currentDay.value;
  let endTime = dayjs(props.endTime);
  endTime = endTime.subtract(1, 'hour');
  tempDay = tempDay.add(3, 'day').set('hour', 0).set('minute', 0);

  if (tempDay.isAfter(endTime)) {
    ElMessage.warning('不能查看结束时间之后')
    return false;
  }

  currentDay.value = tempDay;
  const width = getWidthAbout2Times(props.startTime, tempDay);
  scroller.scrollTo(-width, scroller.y, 400);
};

const getWidthAbout2Times = (start: dayjs.Dayjs, end: dayjs.Dayjs) => {
  const options = {
    scale: props.scale,
    cellWidth: props.cellWidth,
  };
  return _getWidthAbout2Times(start, end, options);
};

const getPositionOffset = (date: dayjs.Dayjs) => {
  const options = {
    scale: props.scale,
    cellWidth: props.cellWidth,
  };
  return _getPositionOffset(date.toString(), beginTimeOfTimeLineToString.value.toString(), options);
};

const cacheSelector = () => {
  selector.value.gantt_leftbar = leftBarWrapper.value;
  selector.value.gantt_table = blocksWrapper.value;
  selector.value.gantt_timeline = headerTimeline.value;
  selector.value.gantt_markArea = marklineArea.value;
};

const releaseSelector = () => {
  for (const key in selector.value) {
    selector.value[key] = null;
  }
};

const handleLeftClickBlock = (event: MouseEvent, rowData: any, blockItem: any) => {
  ganttStore.setCurrentRow(rowData);
  ganttStore.setCurrentBlock(blockItem);
};

const handleRightClickBlock = (event: MouseEvent, rowData: any, blockItem: any) => {
  taskOperation.value = true
  rowOperation.value = false
  menuVisible.value = true
  menuX.value = event.clientX - ganttChart.value.getBoundingClientRect().left + 10
  menuY.value = event.clientY - ganttChart.value.getBoundingClientRect().top
  document.addEventListener("click", hiddenMenu)
  ganttStore.setHandleRow(rowData);
  ganttStore.setHandleBlock(blockItem);
};

const hiddenMenu = () => {
  menuVisible.value = false
}

const handleRightClickRow = (event: MouseEvent, blockRow: any) => {
  taskOperation.value = false
  rowOperation.value = true
  menuVisible.value = true
  menuX.value = event.clientX - ganttChart.value.getBoundingClientRect().left + 10
  menuY.value = event.clientY - ganttChart.value.getBoundingClientRect().top
  document.addEventListener("click", hiddenMenu)
  ganttStore.setTargetBlock(null);
  ganttStore.setTargetRow(blockRow);
};

const moveCurrentBlock = () => {
  ganttStore.setCutBlock(ganttStore.handleBlock);
  ganttStore.setCutRow(ganttStore.handleRow);
};

const pasteBlock = () => {
  if (isObjectNull(ganttStore.cutBlock)) {
    ganttStore.setCurrentBlock(ganttStore.cutBlock);
    emit('dragTask');
  }
};

const switchBlock = () => {
  if (isObjectNull(ganttStore.cutBlock) && isObjectNull(ganttStore.currentBlock) && isObjectNull(ganttStore.handleBlock)) {
    ganttStore.setCurrentBlock(ganttStore.cutBlock);
    ganttStore.setCurrentRow(ganttStore.cutRow);
    ganttStore.setTargetBlock(ganttStore.handleBlock);
    ganttStore.setTargetRow(ganttStore.handleRow);
    emit('dragTask');
  }
};

const isObjectNull = (obj) => {
  return obj && Object.keys(obj).length > 0;
}

const dropToRow = (event: MouseEvent, rowData: any, index: number) => {
  let str = event.dataTransfer.getData('text/json')
  if (str.length > 0) {
    console.log(str)
    // emit('dragTask', str);
    return false;
  }
  if (rowData.id === ganttStore.currentRow.id) return false;
  ganttStore.setDataRow(index);
  ganttStore.setCurrentRow(null);
  ganttStore.setTargetBlock(null);
  ganttStore.setTargetRow(rowData);
  emit('dragTask');
};


</script>

<style lang="scss" scoped>
@import "./gantt.scss";

.Z-Menu {
  position: absolute;
  z-index: 1000;
  border: 1px solid #ccc;
  border-radius: 5px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
  animation: slideDown 0.2s ease-in-out;
  background-color: #fff; // 添加背景色
  //padding: 10px 0; // 添加内边距
  font-size: 14px; // 设置字体大小
  color: #333; // 设置文字颜色
  list-style: none; // 移除列表默认样式
  margin: 0; // 移除外边距
  padding: 0; // 移除内边距（之前设置的是针对整个菜单的）

  ul {
    padding: 0;
    margin: 0;
    list-style: none;
  }

  li {
    padding: 8px 16px; // 添加内边距使列表项更易点击
    cursor: pointer; // 鼠标悬停时显示为手指图标

    &:hover {
      background-color: #f0f0f0; // 悬停时改变背景色
      color: #007bff; // 悬停时改变文字颜色
    }

    & + li {
      border-top: 1px solid #eee; // 为每个列表项添加分隔线
    }
  }
}
</style>
